package com.gitee.wsl.net.client
/*
 * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 */

import android.content.Context
import android.os.Build
import android.security.KeyChain
import com.gitee.wsl.net.client.authentication.AccountSettings
import okhttp3.*
import okhttp3.internal.tls.OkHostnameVerifier
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.Socket
import java.security.KeyStore
import java.security.Principal
import java.util.*
import java.util.concurrent.TimeUnit
import javax.net.ssl.*

class HttpClient private constructor(
    val okHttpClient: OkHttpClient,
    val config: ClientConfig
): AutoCloseable {
    val certManager=config.certManager

    companion object {
        /** max. size of disk cache (10 MB) */
        const val DISK_CACHE_MAX_SIZE: Long = 10*1024*1024

        /** [OkHttpClient] singleton to build all clients from */
        val sharedClient: OkHttpClient = OkHttpClient.Builder()
            // set timeouts
            .connectTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)

            // don't allow redirects by default, because it would break PROPFIND handling
            .followRedirects(false)

            // add User-Agent to every request
            .addNetworkInterceptor(UserAgentInterceptor)

            .build()
    }


    override fun close() {
        okHttpClient.cache?.close()
        certManager.close()
    }

    class Builder(
        val context: Context? = null,
        accountSettings: AccountSettings? = null,
        val logger:Boolean = false,
        val settings: ClientConfig = ClientConfig()
    ) {
        private var certManager: CustomCertManager? = settings.certManager
        private var certificateAlias: String? = null

        private val orig = sharedClient.newBuilder()

        init {
            // add network logging, if requested
            if (logger) {
                val loggingInterceptor = HttpLoggingInterceptor { message -> Timber.d(message) }
                loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
                orig.addInterceptor(loggingInterceptor)
            }

            context?.let {

                try {
                    if (settings.overrideProxy()) {
                        val address = InetSocketAddress(
                            settings.overrideProxyHost(),
                            settings.overrideProxyPort()
                        )

                        val proxy = Proxy(Proxy.Type.HTTP, address)
                        orig.proxy(proxy)
                        Timber.i( "Using proxy", proxy)
                    }
                } catch (e: Exception) {
                    Timber.e(  "Can't set proxy, ignoring", e)
                } finally {
                    // dbHelper.close()
                }

                //if (BuildConfig.customCerts)
                customCertManager(CustomCertManagerImpl( true, !(settings.distrustSystemCertificates())))

            }

            // use account settings for authentication
            accountSettings?.let {
                addAuthentication(accountSettings.uri.host, accountSettings.authToken)
            }
        }

        constructor(context: Context?, host: String?, authToken: String): this(context) {
            addAuthentication(host, authToken)
        }

        fun withDiskCache(): Builder {
            val context = context ?: throw IllegalArgumentException("Context is required to find the cache directory")
            for (dir in arrayOf(context.externalCacheDir, context.cacheDir).filterNotNull()) {
                if (dir.exists() && dir.canWrite()) {
                    val cacheDir = File(dir, "HttpClient")
                    cacheDir.mkdir()
                    Timber.i( "Using disk cache: $cacheDir")
                    orig.cache(Cache(cacheDir, DISK_CACHE_MAX_SIZE))
                    break
                }
            }
            return this
        }

        fun followRedirects(follow: Boolean): Builder {
            orig.followRedirects(follow)
            return this
        }

        fun customCertManager(manager: CustomCertManager) {
            certManager = manager
        }
        fun setForeground(foreground: Boolean): Builder {
            //certManager?.appInForeground = foreground
            return this
        }

        private fun addAuthentication(host: String?, token: String): Builder {
            val authHandler = TokenAuthenticator(host, token)

            orig.addNetworkInterceptor(authHandler)

            return this
        }

        private class TokenAuthenticator internal constructor(internal val host: String?, internal val token: String?) : Interceptor {

            @Throws(IOException::class)
            override fun intercept(chain: Interceptor.Chain): Response {
                var request = chain.request()

                /* Only add to the host we want. */
                if (host == null || request.url.host == host) {
                    if (token != null && request.header(HEADER_AUTHORIZATION) == null) {
                        request = request.newBuilder()
                            .header(HEADER_AUTHORIZATION, "Token $token")
                            .build()
                    }
                }

                return chain.proceed(request)
            }

            companion object {
                protected const val HEADER_AUTHORIZATION = "Authorization"
            }
        }

        fun build(): HttpClient {
            val trustManager = certManager ?: run {
                val factory =TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
                factory.init(null as KeyStore?)
                factory.trustManagers.first() as X509TrustManager
            }

            val hostnameVerifier = certManager?.hostnameVerifier(OkHostnameVerifier)?: OkHostnameVerifier

            var keyManager: KeyManager? = null
            certificateAlias?.let { alias ->
                try {
                    val context = requireNotNull(context)

                    // get provider certificate and private key
                    val certs = KeyChain.getCertificateChain(context, alias) ?: return@let
                    val key = KeyChain.getPrivateKey(context, alias) ?: return@let
                    Timber.d("Using provider certificate $alias for authentication (chain length: ${certs.size})")

                    // create Android KeyStore (performs key operations without revealing secret data to DAVx5)
                    val keyStore = KeyStore.getInstance("AndroidKeyStore")
                    keyStore.load(null)

                    // create KeyManager
                    keyManager = object: X509ExtendedKeyManager() {
                        override fun getServerAliases(p0: String?, p1: Array<out Principal>?): Array<String>? = null
                        override fun chooseServerAlias(p0: String?, p1: Array<out Principal>?, p2: Socket?) = null

                        override fun getClientAliases(p0: String?, p1: Array<out Principal>?) =
                            arrayOf(alias)

                        override fun chooseClientAlias(p0: Array<out String>?, p1: Array<out Principal>?, p2: Socket?) =
                            alias

                        override fun getCertificateChain(forAlias: String?) =
                            certs.takeIf { forAlias == alias }

                        override fun getPrivateKey(forAlias: String?) =
                            key.takeIf { forAlias == alias }
                    }

                    // HTTP/2 doesn't support client certificates (yet)
                    // see https://tools.ietf.org/html/draft-ietf-httpbis-http2-secondary-certs-04
                    orig.protocols(listOf(Protocol.HTTP_1_1))
                } catch (e: Exception) {
                    Timber.e( "Couldn't set up provider certificate authentication", e)
                }
            }

            val sslContext = SSLContext.getInstance("TLS")
            sslContext.init(
                if (keyManager != null) arrayOf(keyManager) else null,
                arrayOf(trustManager),
                null)
            orig.sslSocketFactory(sslContext.socketFactory, trustManager)
            orig.hostnameVerifier(hostnameVerifier)

            return HttpClient(orig.build(), settings)
        }

    }

    private object UserAgentInterceptor : Interceptor {
        private val userAgent = "${BuildConfig.LIBRARY_PACKAGE_NAME} (okhttp3) Android/${Build.VERSION.RELEASE}"

        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
            val locale = Locale.getDefault()
            val request = chain.request().newBuilder()
                .header("User-Agent", userAgent)
                .header("Accept-Language", locale.language + "-" + locale.country + ", " + locale.language + ";q=0.7, *;q=0.5")
                .build()
            return chain.proceed(request)
        }
    }

}